// UI Classes.
// ===========

// The Font Class.
class pp_font {
  PFont font;
  color fontColor;
  int dropShadow;
  
  pp_font (PFont fnt, color rgb, int shadow) {
    font = fnt;
    fontColor = rgb;
    dropShadow = shadow;
  }
}

// UI Builder class.
public class pp_UIBuilder {
  int x, y, w, h, snap, textAlign;
  PImage[] img;
  String fontName;
  pp_UIwidget parent;
  
  public pp_UIBuilder () {}  
  public pp_UIBuilder x(int PosX) {x = PosX; return this;}
  public pp_UIBuilder y(int PosY) {y = PosY; return this;}
  public pp_UIBuilder snap(int snapTo) { snap = snapTo; return this;}
  public pp_UIBuilder img(String spr) {if (uiSpr.get(spr) != null) {img = uiSpr.get(spr);} else {img = null;} return this;}
  public pp_UIBuilder w(int Width) {w = Width; return this;}
  public pp_UIBuilder h(int Height) {h = Height; return this;}
  public pp_UIBuilder font(String Font) {fontName = Font; return this;}
  public pp_UIBuilder align(int Align) {textAlign = Align; return this;}
  public pp_UIBuilder parent(pp_UIwidget par) {parent = par; return this;}
    
  public pp_UIwidget build() {
    return new pp_UIwidget(this);
  }
}

// The Widget Class.
public class pp_UIwidget {
  int x, y, w, h, snap, textAlign, drawX, drawY;
  int parentWidth, parentHeight;
  boolean tiling, visible;
  String text = "";
  PImage[] img;
  pp_font font;
  pp_UIwidget parent;
  
  pp_UIwidget (pp_UIBuilder builder) {
    
    // Load parameters.
    textAlign = builder.textAlign;
    font = fnt.get(builder.fontName);
    visible = true;
    img = builder.img;
    
    // Get h and w.
    if ((builder.w != 0) && (builder.h != 0)) {
      w = builder.w;
      h = builder.h;
      tiling = true;
    } else {
      if (builder.img[0] != null) {
        w = builder.img[0].width;
        h = builder.img[0].height;
        tiling = false;
      } else {
        w = 0;
        h = 0;
      }
    }
    
    // Get parentWidth and parentHeight.
    parent = builder.parent;
    if (parent != null) {
      parentWidth = parent.w;
      parentHeight = parent.h;
    } else {
      parentWidth = pp_matrixWidth;
      parentHeight = pp_matrixHeight;
    }
    
    // Get position.
    snap = builder.snap;
    if (snap == 0) {x = builder.x; y = builder.y;}
    else {pp_UISnap(this, snap);}
    
    // Register methods.
    app.registerMethod("pre", this);
    app.registerMethod("draw", this);
  }
  
  // Change UI sprite.
  void setSprite(String spriteName) {
    if (uiSpr.get(spriteName) != null) {
      img = uiSpr.get(spriteName);
    } else {
      img = null;
    }
    
    if (!tiling) {
      w = img[0].width;
      h = img[0].height;
    }
    
    if (snap != 0) {pp_UISnap(this, snap);}
  }
  
  // Change text.
  void setText(String txt) {
    text = txt;
  }
  
  void pre() {
    if (parent != null) {
      // Update position if parent shifted.
      drawX = parent.x + x;
      drawY = parent.y + y;
    
      // Determine parent visibility.
      visible = parent.visible;
    }
    
    if (visible){
      // Draw UI image to UIRenderBuffer.
      if (img != null) {
      
        // If tiling.
        if (tiling) {
          pp_UIdrawSpriteTiling (drawX, drawY, w, h, img[0]);
        
        // Else, render single image.
        } else {pp_UIdrawSprite (drawX, drawY, img[0]);}
      }
    }
  }
    
  void draw() {
    
    if (visible) {
      
      // If there is text...
      if (text.length() > 0) {
        int drawTextX, drawTextY;
        
        // Set Alignment.
        pp_UITextAlign(textAlign);
        
        if (parent != null) {
          drawTextX = (x + parent.x) * pp_minScale;
          drawTextY = (y + parent.y) * pp_minScale;
        } else {
          drawTextX = x * pp_minScale;
          drawTextY = y * pp_minScale;
        }
      
        // Set font parameters.
        if (font != null) {
          // Set font.
          textFont(font.font);
        
          // Render dropshadow.
          if (font.dropShadow > 0){
            fill(pp_background);
            text(text, drawTextX + pp_minScale, drawTextY + pp_minScale, (w + 1) * pp_minScale, (h + 1) * pp_minScale);
          }
        
          // Set fill.
          fill(font.fontColor);
        }
        
        // Draw text.
        text(text, drawTextX, drawTextY,  w * pp_minScale, h * pp_minScale);
      }
    }
  }
}

// UI Functions.
// =============

// Load sprite for UI purposes.
PImage pp_UISpriteLoad (String filename) {
  
  // If filename not provided, return null.
  if (filename.length() <= 0) {
    return null;
  
  // If filename provided...
  } else {
    if (pp_fileExists(filename)) {
      
      // Load image file.
      PImage sprite = loadImage(filename);
      return sprite;
      
    // If file is not found, report.
    } else {
      println("Image " + dataPath(filename) + " not found.");
      return null;
    }
  }
}

// Draw a pixel onto UIRenderBuffer.
void pp_UIdrawPixel (int x, int y, color rgb) {
  
  // Prevent out-of-bounds.
  int i = constrain(x + (y * pp_matrixWidth), 0, pp_UIRenderBuffer.length-1);
  
  // Test for alpha value of pixel.
  switch ((rgb >> 24) & 0xFF){
    
    // If pixel is completely transparent, ignore.
    case 0:
      break;
  
    // If pixel is opaque, replace original pixel.
    case 255:
      pp_UIRenderBuffer[i] = rgb;
      break;    
    
    // If pixel has alpha value, modify original pixel instead.
    default:
      pp_UIRenderBuffer[i] = 
        ((constrain(((rgb >> 24) & 0xFF) + ((pp_UIRenderBuffer[i] >> 24) & 0xFF), 0, 255)) << 24)  |
        ((pp_alphaCalc((rgb >> 16) & 0xFF, (pp_UIRenderBuffer[i] >> 16) & 0xFF, (rgb >> 24) & 0xFF)) << 16) |
        ((pp_alphaCalc((rgb >> 8) & 0xFF, (pp_UIRenderBuffer[i] >> 8) & 0xFF, (rgb >> 24) & 0xFF)) << 8) |
        (pp_alphaCalc(rgb & 0xFF, pp_UIRenderBuffer[i] & 0xFF, (rgb >> 24) & 0xFF));
      break;
  }
}

// Draw a sprite onto UIRenderBuffer.
void pp_UIdrawSprite (int x, int y, PImage img) {
  
  // Test if all edges are on screen.
  int testResult = pp_toInt(pp_UITestOnScreen(x, y)) + pp_toInt(pp_UITestOnScreen(x + img.width, y + img.height));
  
  switch (testResult) {
    
    // Ignore if not on screen.
    case 0:
      break;
    
    // Partially on screen.
    case 1:
      // Get margins.
      int startX = (x < 0) ? abs(x) : 0;
      int endX = (x + img.width >= pp_matrixWidth) ? pp_matrixWidth - x : img.width;
      int startY = (y < 0) ? abs(y) : 0;
      int endY = (y + img.height >= pp_matrixHeight) ? pp_matrixHeight - y : img.height;
  
      for (int i = startX; i < endX; i++) {
        for (int k = startY; k < endY; k++) {
          pp_UIdrawPixel(i + x, k + y, img.pixels[i + (k * img.width)]);
        }
      }
      break;
      
    case 2:
      for (int i = 0; i < img.width; i++) {
        for (int k = 0; k < img.height; k++) {
          pp_UIdrawPixel(i + x, k + y, img.pixels[i + (k * img.width)]);
        }
      }
      break;
  }
}

// Draw a tiling sprite onto UIRenderBuffer.
void pp_UIdrawSpriteTiling (int x, int y, int w, int h, PImage img) {
  
  // Determine tile dimensions.
  int tileWidth = int(img.width/3);
  int tileHeight = int(img.height/3);
  int textureX = 0;
  int textureY = 0;
  
  int startX = 0;
  int startY = 0;
  int endX = w;
  int endY = h;
  
  // Test if all edges are on screen.
  int testResult = pp_toInt(pp_UITestOnScreen(x, y)) + pp_toInt(pp_UITestOnScreen(x + w, y + h));
  
  // If partially on screen, find cropped dimensions.
  if (testResult == 1) {
    startX = (x < 0) ? abs(x) : 0;
    endX = (x + w >= pp_matrixWidth) ? pp_matrixWidth - x : w;
    startY = (y < 0) ? abs(y) : 0;
    endY = (y + h >= pp_matrixHeight) ? pp_matrixHeight - y : h;
  }
  
  if (testResult != 0) {
    // Draw tiled image to UIRenderBuffer.
    // Process each row.
    for (int i = startX; i < endX; i++) {
    
      // Determine x-coord on texture.
      if (i < tileWidth) {
        textureX = i;
      } else {
        if (i > w - tileWidth) {
          textureX = (i - w + tileWidth) + (2 * tileWidth);
        } else {
          textureX = ((i - tileWidth) % tileWidth) + tileWidth;
        }
      }
    
      // Process each column.
      for (int k = startY; k < endY; k++) {
        
        // Determine y-coord on texture.
        if (k < tileHeight) {
          textureY = k;
        } else {
          if (k > h - tileHeight) {
            textureY = (k - h + tileHeight) + (2 * tileHeight);
          } else {
            textureY = ((k - tileHeight) % tileHeight) + tileHeight;
          }
        }
      
        // Draw each pixel to UIRenderBuffer.
        pp_UIdrawPixel(i + x, k + y, img.pixels[textureX + (textureY * img.width)]);
      }
    }
  }
}

// Reposition based on snapping.
void pp_UISnap (pp_UIwidget ui, int snap){
  switch (snap) {        
    // Snap top-left.
    case 1:
      ui.x = 0;
      ui.y = 0;
      break;
        
    // Snap top-center.
    case 2:
      ui.x = int(ui.parentWidth/2 - ui.w/2);
      ui.y = 0;
      break;
        
    // Snap top-right.
    case 3:
      ui.x = ui.parentWidth - ui.w;
      ui.y = 0;
      break;
      
    // Snap middle-left.
    case 4:
      ui.x = 0;
      ui.y = int(ui.parentHeight/2 - ui.h/2);
      break;
      
    // Snap middle-center.
    case 5:
      ui.x = int(ui.parentWidth/2 - ui.w/2);
      ui.y = int(ui.parentHeight/2 - ui.h/2);
      break;
      
    // Snap middle-right.
    case 6:
      ui.x = ui.parentWidth - ui.w;
      ui.y = int(ui.parentHeight/2 - ui.h/2);
      break;
      
    // Snap bottom-left.
    case 7:
      ui.x = 0;
      ui.y = ui.parentHeight - ui.h;
      break;
      
    // Snap bottom-center.
    case 8:
      ui.x = int(ui.parentWidth/2 - ui.w/2);
      ui.y = ui.parentHeight - ui.h;
      break;
      
    // Snap bottom-right.
    case 9:
      ui.x = ui.parentWidth - ui.w;
      ui.y = ui.parentHeight - ui.h;
      break;
  }
}

// Set textAlign.
void pp_UITextAlign(int textAlign) {
  switch (textAlign) {          
    // No alignment. Default to top-left.
    case 0: textAlign(LEFT, TOP); break;
        
    // Align top-left.
    case 1: textAlign(LEFT, TOP); break;
      
    // Align top-center.
    case 2: textAlign(CENTER, TOP); break;
      
    // Align top-right.
    case 3: textAlign(RIGHT, TOP); break;
      
    // Align middle-left.
    case 4: textAlign(LEFT, CENTER); break;
      
    // Align center.
    case 5: textAlign(CENTER, CENTER); break;
      
    // Align middle-right.
    case 6: textAlign(RIGHT, CENTER); break;
      
    // Align bottom-left.
    case 7: textAlign(LEFT, BOTTOM); break;
      
    // Align bottom-center.
    case 8: textAlign(CENTER, BOTTOM); break;
      
    // Align bottom-right.
    case 9: textAlign(RIGHT, BOTTOM); break;
      
    default: textAlign(LEFT, TOP); break;
  }
}

// Test if pixel is onscreen.
boolean pp_UITestOnScreen (int x, int y) {
  if ((x >= 0) && (x < pp_matrixWidth) && (y >= 0) && (y < pp_matrixHeight)) {
    return true;
  } else {
    return false;
  }
}
